#include "utils/bundle-include.hpp"

// #include "bpp/bpp_mithril.hpp"

using namespace cas;

const std::string policyName[] = {"none", "all", "miss", "recent"};

// global options
std::string optEviction = ""; /**< evction type, only LRU */
/**
 * \brief Mithril policy type
 * 0: none
 * 1: all
 * 2: on miss
 * 3: recent miss
 */
int32_t policy = 0;
std::string optTrace = "";         /**< trace file path */
uint32_t cacheSizeArg = 16 * 1024; /**< 16K -> 8MB */

// global vars
TraceMsr trace;
Mithril mithril;

static inline void print_hit_ratio(const CacheEvictionStatistic &stat)
{
    printf("[lru] %ld, hit: %lf (%ld / %ld)\n",
           stat.requestNumber,
           stat.getHitRatio(),
           stat.requestHitSize,
           stat.requestSize);
}

void mithril_prepare(const CacheRequest &req, const bool &was_hit, const size_t &index)
{
    const size_t NREC = 10;
    static size_t lastIndex = 0;

    if (policy == 1)
    {
        // always record
        mithril.record(req);
    }
    else if (!was_hit)
    {
        if (policy == 2)
        {
            // record on cache miss
            mithril.record(req);
        }
        else
        {
            // on recent miss, policy == 3
            size_t spos = (lastIndex + NREC > index) ? lastIndex : (index - NREC);
            for (size_t i = spos; i < index; i++)
            {
                auto &item = trace.data[i];
                CacheRequest r(CacheRequest::CacheRequestPrefetch, item.devno, item.lba, item.size);
                mithril.record(r);
            }
            lastIndex = index;
        }
    }
    return;
}

int main(int argc, char *argv[])
{
    // parsing command line
    CommandLine cmd;
    cmd.AddNonOption("eviction", "Evction type", optEviction);
    cmd.AddNonOption("prefetch", "Mithril record policy", policy);
    cmd.AddNonOption("traceFile", "Path to trace file", optTrace);
    cmd.AddNonOption("cacheSize", "Number of cache nodes", cacheSizeArg);
    cmd.Parse(argc, argv);
    std::cout << "# cache size: " << cacheSizeArg << std::endl
              << "# mithril policy: " << policy << " " << policyName[policy] << std::endl;

    // LogComponentEnableAll(LOG_WARN);
    // LogComponentPrintList();

    // read in trace
    trace.setPath(optTrace);
    trace.readFile();
    const size_t traceLines = trace.dataSize();
    std::cout << "# num of lines in trace: " << trace.dataSize() << std::endl;

    // LRU
    CacheEvictionLru lru(cacheSizeArg);
    CacheRequest req(CacheRequest::CacheRequestTypeLength);

    // timing
    chrono_clock_t start, startAll;
    chrono_duration_t mithrilTime(0), allTime(0);

    // states
    bool was_hit = false; // required by Mithril lazy-recording
    std::vector<CachePage> predict;

    if (policy)
    {
        // set mithril params
        mithril.setParamSupport(2, 8);
        mithril.setRecordSize(100000);
        mithril.setMiningSize(2048);
        mithril.setPrefetchSize(64, 65536);
        mithril.setLookAheadRange(40);

        mithril.init();
    }

    // run trace
    startAll = chrono_now();
    for (size_t i = 0; i < traceLines; i++)
    {
        const auto &item = trace.data[i];
        // io_info req_io = switch_to_io_info(item.devno, item.lba, item.size);

        start = chrono_now();
        if (policy)
        {
            req.page.devno = item.devno;
            req.page.lba = item.lba;
            req.size = item.size;
            mithril_prepare(req, was_hit, i);
            /* always prefetch */
            predict = mithril.prefetch(req);
        }
        mithrilTime += chrono_now() - start;

        if (lru.stat.requestNumber % 50000 == 0)
        {
            print_hit_ratio(lru.stat);
            if (policy)
            {
                mithril.print();
            }
        }

        // cache eviction
        req.type = CacheRequest::CacheRequestLookup;
        req.page.devno = item.devno;
        req.page.lba = item.lba;
        req.size = item.size;
        was_hit = lru.lookup(req) > 0;

        if (policy)
        {
            req.type = CacheRequest::CacheRequestPrefetch;
            req.size = 1;
            for (const auto &it : predict)
            {
                req.page.devno = it.devno;
                req.page.lba = it.lba;
                lru.lookup(req);
            }
        }
    }

    // finish
    allTime = chrono_now() - startAll;
    printf("[time] all %10lf mithril %10lf\n", allTime.count(), mithrilTime.count());

    print_hit_ratio(lru.stat);
    if (policy)
    {
        mithril.print();
    }

    return 0;
}
